/**
 * PANDA 3D SOFTWARE
 * Copyright (c) Carnegie Mellon University.  All rights reserved.
 *
 * All use of this software is subject to the terms of the revised BSD
 * license.  You should have received a copy of this license along
 * with this source code in a file named "LICENSE."
 *
 * @file eggToC.cxx
 * @author drose
 * @date 2001-08-03
 */

#include "eggToC.h"

#include "eggVertexPool.h"
#include "eggVertex.h"
#include "eggPolygon.h"
#include "eggPrimitive.h"
#include "eggGroupNode.h"
#include "eggPolysetMaker.h"
#include "eggBin.h"
#include "string_utils.h"

using std::ostream;
using std::string;

/**
 *
 */
EggToC::
EggToC() :
  EggToSomething("C", ".c", true, true)
{
  set_program_brief("convert .egg geometry into compilable C tables");
  set_program_description
    ("This program reads Egg files and outputs code that will almost "
     "compile as a C or C++ program.  You get to define the data structures "
     "for the program after the fact; the program only generates tables "
     "of vertices and polygons.");

  // -f is always in effect for egg2c.  It doesn't make sense to provide it as
  // an option to the user.
  remove_option("f");

  add_option
    ("v", "", 0,
     "Generate a table of vertex positions.",
     &EggToC::dispatch_none, &_vertices);

  add_option
    ("u", "", 0,
     "Generate a table of UV's per each vertex.",
     &EggToC::dispatch_none, &_uvs);

  add_option
    ("vn", "", 0,
     "Generate a table of normals per each vertex.",
     &EggToC::dispatch_none, &_vertex_normals);

  add_option
    ("vc", "", 0,
     "Generate a table of colors per each vertex.",
     &EggToC::dispatch_none, &_vertex_colors);

  add_option
    ("p", "", 0,
     "Generate a table of polygons that index into the above tables.",
     &EggToC::dispatch_none, &_polygons);

  add_option
    ("pn", "", 0,
     "Generate a table of normals per each polygon.",
     &EggToC::dispatch_none, &_polygon_normals);

  add_option
    ("pc", "", 0,
     "Generate a table of colors per each polygon.",
     &EggToC::dispatch_none, &_polygon_colors);

  add_option
    ("t", "", 0,
     "Output only triangles by subdividing higher-order polygons.",
     &EggToC::dispatch_none, &_triangulate_polygons);
}

/**
 *
 */
void EggToC::
run() {
  nout << "Removing invalid primitives.\n";
  int num_removed = _data->remove_invalid_primitives(true);
  nout << "  (" << num_removed << " removed.)\n";

  if (_triangulate_polygons) {
    nout << "Triangulating polygons.\n";
    int num_produced = _data->triangulate_polygons(~0);
    nout << "  (" << num_produced << " triangles produced.)\n";
  }

  _data->apply_texmats();
  _data->flatten_transforms();
  _data->remove_unused_vertices(true);

  // Collect all the polygons together into polysets.
  EggPolysetMaker pmaker;
  pmaker.set_properties(0);
  pmaker.make_bins(_data);

  get_output()
    << "/*\n"
    << " * Generated by:\n"
    << " * " << get_exec_command() << "\n"
    << " *\n"
    << " */\n\n";

  _next_vpool_index = 0;
  _next_bin_index = 0;
  traverse(_data);
}

/**
 *
 */
void EggToC::
traverse(EggNode *node) {
  if (node->is_of_type(EggVertexPool::get_class_type())) {
    write_vertex_pool(DCAST(EggVertexPool, node));

  } else if (node->is_of_type(EggBin::get_class_type())) {
    write_bin(DCAST(EggBin, node));

  } else if (node->is_of_type(EggGroupNode::get_class_type())) {
    EggGroupNode *group = DCAST(EggGroupNode, node);

    EggGroupNode::const_iterator ci;
    for (ci = group->begin(); ci != group->end(); ++ci) {
      traverse(*ci);
    }
  }
}

/**
 *
 */
void EggToC::
write_vertex_pool(EggVertexPool *vpool) {
  int highest_index = vpool->get_highest_index();
  int i;

  ostream &out = get_output();
  out << "/* Vertex pool index " << _next_vpool_index
      << ": " << vpool->get_name() << " */\n";
  _vertex_pools[vpool] = _next_vpool_index;
  _next_vpool_index++;

  if (_vertices) {
    out << "/* Vertex definition for " << vpool->get_name() << " */\n"
        << "vertex vertices_" << vpool->get_name() << "[" << highest_index
        << "] = {\n";
    for (i = 0; i < highest_index; i++) {
      EggVertex *vert = vpool->get_vertex(i);
      if (vert == nullptr) {
        out << "  vertex(),  /* " << i << " */\n";
      } else {
        LPoint4d p = vert->get_pos4();
        switch (vert->get_num_dimensions()) {
        case 1:
          out << "  vertex(" << p[0] << "),  /* " << i << " */\n";
          break;

        case 2:
          out << "  vertex(" << p[0] << ", " << p[1]
              << "),  /* " << i << " */\n";
          break;

        case 3:
          out << "  vertex(" << p[0] << ", " << p[1] << ", " << p[2]
              << "),  /* " << i << " */\n";
          break;

        case 4:
          out << "  vertex(" << p[0] << ", " << p[1] << ", " << p[2]
              << ", " << p[3] << "),  /* " << i << " */\n";
          break;

        default:
          out << "  vertex(),   /* error */\n";
        }
      }
    }
    out << "};\n\n";
  }

  if (_uvs) {
    out << "/* UV's for " << vpool->get_name() << " */\n"
        << "uv uvs_" << vpool->get_name() << "[" << highest_index
        << "] = {\n";
    for (i = 0; i < highest_index; i++) {
      EggVertex *vert = vpool->get_vertex(i);
      if (vert == nullptr || !vert->has_uv()) {
        out << "  uv(),  /* " << i << " */\n";
      } else {
        LTexCoordd uv = vert->get_uv();
        out << "  uv(" << uv[0] << ", " << uv[1]
            << "),  /* " << i << " */\n";
      }
    }
    out << "};\n\n";
  }

  if (_vertex_normals) {
    out << "/* Vertex normals for " << vpool->get_name() << " */\n"
        << "normal normals_" << vpool->get_name() << "[" << highest_index
        << "] = {\n";
    for (i = 0; i < highest_index; i++) {
      EggVertex *vert = vpool->get_vertex(i);
      if (vert == nullptr || !vert->has_normal()) {
        out << "  normal(),  /* " << i << " */\n";
      } else {
        LNormald n = vert->get_normal();
        out << "  normal(" << n[0] << ", " << n[1] << ", " << n[2]
            << "),  /* " << i << " */\n";
      }
    }
    out << "};\n\n";
  }

  if (_vertex_colors) {
    out << "/* Vertex colors for " << vpool->get_name() << " */\n"
        << "color colors_" << vpool->get_name() << "[" << highest_index
        << "] = {\n";
    for (i = 0; i < highest_index; i++) {
      EggVertex *vert = vpool->get_vertex(i);
      if (vert == nullptr || !vert->has_color()) {
        out << "  color(),  /* " << i << " */\n";
      } else {
        LColor c = vert->get_color();
        out << "  color(" << c[0] << ", " << c[1] << ", " << c[2]
            << ", " << c[3] << "),  /* " << i << " */\n";
      }
    }
    out << "};\n\n";
  }
}


/**
 *
 */
void EggToC::
write_bin(EggBin *bin) {
  ostream &out = get_output();
  string bin_name = bin->get_name();
  if (bin_name.empty()) {
    bin_name = format_string(_next_bin_index);
    _next_bin_index++;
  }

  out << "/* Polygon group " << bin_name << " */\n";

  size_t num_children = bin->size();

  if (_polygons) {
    out << "/* Polygon definitions for " << bin_name << " */\n";
    string prim_type = "polygon";
    if (_triangulate_polygons) {
      prim_type = "triangle";
    }

    out << prim_type << " polys_" << bin_name << "[" << num_children
        << "] = {\n";

    if (_triangulate_polygons) {
      out << "  /* vpool index, vertex0, vertex1, vertex2 */\n";
    } else {
      out << "  /* vpool index, num vertices, vertex0, vertex1, vertex2, ... */\n";
    }

    EggGroupNode::const_iterator ci;
    size_t prim_index = 0;
    for (ci = bin->begin(); ci != bin->end(); ++ci) {
      EggNode *child = (*ci);
      if (!child->is_of_type(EggPrimitive::get_class_type())) {
        out << "  " << prim_type << "(),   /* error */\n";
      } else {
        EggPrimitive *prim = DCAST(EggPrimitive, child);
        EggVertexPool *vpool = prim->get_pool();
        int vpool_index = -1;
        VertexPools::const_iterator pi = _vertex_pools.find(vpool);
        if (pi != _vertex_pools.end()) {
          vpool_index = (*pi).second;
        }

        out << "  " << prim_type << "(" << vpool_index;
        if (!_triangulate_polygons) {
          out << ", " << prim->size();
        }
        EggPrimitive::const_iterator vi;
        for (vi = prim->begin(); vi != prim->end(); ++vi) {
          EggVertex *vert = (*vi);
          out << ", " << vert->get_index();
        }
        out << "),  /* " << prim_index << " */\n";
        prim_index++;
      }
    }
    out << "};\n\n";
  }

  if (_polygon_normals) {
    ostream &out = get_output();
    out << "/* Polygon normals for " << bin_name << " */\n";
    out << "normal polys_" << bin_name << "[" << num_children
        << "] = {\n";

    EggGroupNode::const_iterator ci;
    size_t prim_index = 0;
    for (ci = bin->begin(); ci != bin->end(); ++ci) {
      EggNode *child = (*ci);
      if (!child->is_of_type(EggPrimitive::get_class_type())) {
        out << "  normal(),   /* error */\n";
      } else {
        EggPrimitive *prim = DCAST(EggPrimitive, child);
        if (!prim->has_normal()) {
          out << "  normal(),   /* " << prim_index << " */\n";
        } else {
          LNormald n = prim->get_normal();
          out << "  normal(" << n[0] << ", " << n[1] << ", " << n[2]
              << "),  /* " << prim_index << " */\n";
        }
        prim_index++;
      }
    }
    out << "};\n\n";
  }

  if (_polygon_colors) {
    ostream &out = get_output();
    out << "/* Polygon colors for " << bin_name << " */\n";
    out << "color polys_" << bin_name << "[" << num_children
        << "] = {\n";

    EggGroupNode::const_iterator ci;
    size_t prim_index = 0;
    for (ci = bin->begin(); ci != bin->end(); ++ci) {
      EggNode *child = (*ci);
      if (!child->is_of_type(EggPrimitive::get_class_type())) {
        out << "  color(),   /* error */\n";
      } else {
        EggPrimitive *prim = DCAST(EggPrimitive, child);
        if (!prim->has_color()) {
          out << "  color(),   /* " << prim_index << " */\n";
        } else {
          LColor c = prim->get_color();
          out << "  color(" << c[0] << ", " << c[1] << ", " << c[2]
              << ", " << c[3] << "),  /* " << prim_index << " */\n";
        }
        prim_index++;
      }
    }
    out << "};\n\n";
  }
}


int main(int argc, char *argv[]) {
  EggToC prog;
  prog.parse_command_line(argc, argv);
  prog.run();
  return 0;
}
